/*
Job calculus:

                    +--                                                 +--
                    |           +--             +-----> dependent job   |
                    |           |               |                       |
                    |         t1|       --------+                       +--
                    |           |
          Job       |           +--
                    |
                    |           +--
                    |           |
                    |         t2|
                    |           |
                    |           +--
                    |           .
                    |           .
                    |           .
                    |
                    |           +--
                    |           |
                    |       t(n)|
                    |           |
                    |           +--
                   T+--
                   
                   
A task is a unit of work, done asynch
A job consists of 1 or more tasks
A job can be dependent on another job
An individual task within a job can have:
    a) inter-dependency: dependent on other jobs
    b) intra-dependency: dependent on task within the same job

each task will have:
    1) the actual work definition.. function/method to execute
    2) a periodic completion check and 
    3) an overall abort to ensure no infinte looping on the completion check,
        ie. if the task doesnt complete within a set time then it is aborted.

*thoughts*******************
    - tasks cannot be 'paused' so each task once started will have to run through to completion
    - can a job 'suspend' itself by saving state and then reinstating itself from that state and continue running?
        - job needs a notion of when to 'suspend'
        - what are the state variables?
****************************

task's responsibility
 - ensure start conditions are met... eg. are there dependent jobs? are jb related resources available?
 - if a task needs to spawn other tasks, then ensure it appends the tasks to the task queue, either as dependents or not; as needed
 - set task status so that other jobs can ascertain status of the job
 - during the run,, that task may optianally set in-progress status.. these are well-behaved citizens.
 - ensure end conditions are obliged.. eg releasing resources that other jobs may require,
 - remove itself off the task queue once completed
 - 
 
 
    while (tasks exist in queue) {
        workon( not started task at top of queue);  -->|  if (pre-start conditions !met) return false
        if false keep task status as not started       |  else start task and return true;
        else set task to in-progress
        
    }



PM - process mgmt
what about tasks under the control of the browser? .. eg. using the script tag approach to loading a file.
    - for these you need to initiate it, and a way of checking for completion.
    - quite tricky if there are dependent tasks downstream .. they need to check for completion of upstream tasks
    
            T1: load f1 widget code
            T2: build widget.
            
    T2 cannot start until T1 is complete.  T1 is kicked off as an async process by the browser (not you) and when complete
    can be made to set a 'flag'.  T2 is kicked off immediately after T1 kickoff, but it cannot do it's real work, until the
    flag is set.  So, T2 has to check for the flag periodically.
    
        T1 kicked off   -----------------> set flag X
        T2 kicked off <---------+
            check flag X        |
            if set continue     |
            else set timer      |
            to kick off T2 -----+
            
    - for tasks fully under your control...
        the jobs in a 
        
*/

PM = function() {
    var _pStack = [];
    
    // a process is a dircted acyclic graph of other processes and/or tasks
    // eventually a process will map down to a set of acyclic connected tasks
    // stack = [t1, t2, p1[t1, t2, ..], t3, p2[t1, t2, t3, p1[t4, ...]], t4,....]
    // note that p1 stack is a process containing a set of tasks (note t1 here may not be the same as the t1 in the outer stack
}

var ZIP.PM.task = function() {
// can be used to define a single task or a job with a list of tasks.
// every call to this object creates a new job.
//
    var liveTasks = {},     // a universal list of tasks grouped by their job id --> { jobID: [t1, t2, ... t(n)] }
        chkIntvl = 100,     // milliseconds.  we'll get this from the threshold object from the global config
        abrtTimeout = 5000;
    
    function _constructor(taskdefn, completeness, abort) {
        var jobID = ZIP.System.uid(''),
            jobTaskList = [];
        liveTasks.push[id];
        
    }
    
    var proto = {
        init: function taskSetup(taskList) {},           // taskList is composed of  { task: taskdefn, complete: completeCheckfunc, abort: abortfunc }
                                                        // per task
        addTask: function(task) { },                    // add { task: taskdefn, complete: completeCheckfunc, abort: abortfunc } object to this job
        start: function() {},
        kill: function() {},        // explicit abort
        getID: function() {}        // return jobID
        getTasks: function() {}     // return array of task IDs for this job
    };
    
}();


var ZIP.Asynch.scheduler = ( function() {
    var taskQueue = [];
    
    
    run: function() {
        
        while (task = getThenextActiveTask()) {
            if (task.precondsMet()) {
                start(task); checkFinish(task); setAbort(task);
            }
        }
    }

} ) ();








var ProcessStack = function(tagID)  {
    var rows = cols = 1,  // initial size
        i = j = 0,
        _pStack = new Array(rows),
        _pStackGraphs = [],
        _pStackValid = true
        _taskComplete = {},
        _taskPriority = {};

    // initialise the stack
    for (; i < cols; i++) {
        _pStack[i]=new Array(rows);
    }
    _pStack[0][0] = tagID;

    function _display() {
        var r = 0, nd;
        console.log("Stack picture: ", tagID);
        for (; r< rows; r++) {
            console.log(_pStack[r]);
        }

        console.log('Stack is ', _pStackValid?'VALID':'NOT VALID');
        for (r=0; r<rows-1; r++) {
            nd = _pStack[r+1][0];
            console.log(_pStackGraphs[r], _taskComplete[nd], _taskPriority[nd]);
        }

        return this;
    }
    function _addCol(tag) {
        var i = 0;

        // add the new column.. extend each row by 1 element  
        for (; i < rows; i++) {
            _pStack[i].push(false);
        }
        _pStack[0][cols] = tag;
        cols++;;
    }
    function _addRow(tag) {
        var i = 1;

        _pStack[rows] = new Array(cols);    // add the new row;
        _pStack[rows][0] = tag;
        for (; i < cols; i++) {
            _pStack[rows][i] = false;    // no dependency by default
        }
        rows++;
    }
    function _addEdge(u, v) {
        // by edge we mean dependency relationship
        // u depends on completion of v
        // edges are defined in row orientation
        if ( u == v ) return this;

        var r = c = 0;

        // get the (u,v) cell in _pStack
        for (; r < rows; r++) {
            if (_pStack[r][0] == u ) break;
        }
        for (; c < cols; c++) {
            if (_pStack[0][c] == v ) break;
        }
        _pStack[r][c] = true;

        return this;
    }
    function _contains(aList, item) {
        return (aList.indexOf(item) > -1)
    }
    function _getEdges(u) {
        // edges are row orientated
        var edgeList = [], rootList = [], r = 0, valid = true;
        rootList.push(u);

        function getNodeEdges(node) {
            var c, r = 1, nd;
            for (; r < rows; r++) {
                if (_pStack[r][0] == node ) break;
            }

            c = 1; // start on the 2nd column as the 1st column is the task ID.
            for (; c < cols; c++) {
                nd = _pStack[0][c];

                if (_pStack[r][c] && valid) {
                    if ( (c!=r) && !_contains(rootList, nd) ) {
                        // don't bother to add to edgList if the node is there already
                        if (!_contains(edgeList, nd) ) edgeList.push(nd);
                        rootList.push(nd);

                        if (!getNodeEdges(nd)) { valid = false; break; }
                    }
                    else {
                        try { throw new Error(['Invalid Process Stack: ',
                            nd, ' and ', node, ' are cyclic'].join('')); }
                        catch (e) { console.log(e.name, e.message); }
                        valid = false;
                        break;
                    }
                }
            }
            // remove the last entry in rootList if you're here.. as it's a leaf node
            // and we should'nt check against it further in our recursive calls

            return valid && rootList.pop();
        }

        getNodeEdges(u);        // start the getting
        return [valid, edgeList];
    }

    function _validateStack() {
        var r, list, nd, i = 0;

        for (r=1; r < rows; r++) {
            nd = _pStack[r][0];
            list = _getEdges(nd);
            _pStackGraphs.push([list[0], nd, list[1]]);
            _taskComplete[nd] = false;
            _taskPriority[nd] = 1;
        }
        for (r=0; r<rows-1; r++) {
            _pStackValid = true;
            if ( !_pStackGraphs[r][0] ) {
                _pStackValid = false;
                break;
            }
        }

        for (r = 1; r<rows; r++) {
            list = _pStackGraphs[r-1][2];
            for (; i < list.length; i++) {
                _taskPriority[list[i]]++;
            }
        }
        return this;
    }

    return {
        addTask: function(tag) { _addRow(tag); _addCol(tag); return this; },
        display: _display,
        addEdge: _addEdge,
        getEdges: _getEdges,
        validateStack: _validateStack
    }
};


var m = ProcessStack('P1');
m.addTask('t1').addTask('t2').addTask('t3').addTask('t4').addTask('t5');
m.addEdge('t1', 't2').addEdge('t1', 't3');
m.addEdge('t2', 't3');
m.addEdge('t3', 't5');
m.addEdge('t4', 't1');

////m.addEdge('t3', 't2');
m.validateStack().display();

////console.dir(m.getEdges('t1'));
////console.dir(m.getEdges('t1'));



//////////////////////////////////////////////////////////////////////////////////////
var notSTARTED = 0,
    inPROGRESS = 1,
    COMPLETE = 2,
    STOPPED = 3,
    taskStatus = notSTARTED,
    fnNextTick = function(f) {
        console.log('Next Tick ...');
        setTimeout(f, 1);
    },
    getuid = 1,
    wait = function(ms){
        ms += new Date().getTime();
        while (new Date() < ms){}
    };

var Task = function(tName, tProc) {
    var id = getuid++,
        wrappedTask;

    wrappedTask = ( function(task) {
        return function() {
            // execute the task and set status.  The task must return 1 of 2 vaues
            // inPROGRESS, COMPLETE
            return (taskStatus != COMPLETE) && (taskStatus = task());
        }
    } )(tProc);

    function _id() { return id }
    function _name() { return tName }
    function _isComplete() { return (taskStatus == COMPLETE) }
    function _notStarted() { return (taskStatus == notSTARTED) }
    function _inProgress() { return (taskStatus == inPROGRESS) }
    function _isStopped() { return (taskStatus == STOPPED) }
    function _stop() { taskStatus = STOPPED; return this; }

    function _start() {
        if ( _isComplete() ) return this;    // job is complete
        taskStatus = inPROGRESS;

        // execute the task and set 'asynch' processing in place for the task
        wrappedTask();
        _inProgress() && fnNextTick(_start)
        return this;
    }

    return {
        id: _id,
        isComplete: _isComplete,
        notStarted: _notStarted,
        inProgress: _inProgress,
        start: _start,
        stop: _stop
    }
    
}
var stillToGo = 0;
var egTask = Task('egTask', function() {
    // the workload... note most internal tasks are sequential.
    // the only ones that are truly asynch are XHR types or
    // loading files related (eg via the script tag)

    console.log('egTask.. wait a mo', stillToGo);
    wait(1000); // just wait
    console.log('yawn');

    // return status.. simulate asynch condition.. here we relinquish
    stillToGo++;
    if ( stillToGo < 10 ) return inPROGRESS;
    return COMPLETE;
    
});
var andMe = Task('andMe', function() {
    // the workload... note most internal tasks are sequential.
    // the only ones that are truly asynch are XHR types or
    // loading files related (eg via the script tag)

    console.log('andMe.. wait a mo', stillToGo);
    wait(1000); // just wait
    console.log('yawn');

    // return status.. simulate asynch condition.. here we relinquish
    stillToGo++;
    if ( stillToGo < 10 ) return inPROGRESS;
    return COMPLETE;
    
});
egTask.start();
andMe.start();
//////////////////////////////////////////////////////////////////////////////////////

var notSTARTED = 0,
    inPROGRESS = 1,
    COMPLETE = 2,
    STOPPED = 3,
    taskStatus = notSTARTED,
    ctr = 0;
    fnNextTick = function(f) {
        if (ctr++%100 == 0) console.log('Next Tick ...', ctr, diff());
        setTimeout(f, 1);
    },
    getuid = 1,
    wait = function(ms){
        ms += new Date().getTime();
        while (new Date() < ms){}
    };

var Task = function(tName, tProc) {
    var id = getuid++,
        wrappedTask;

    wrappedTask = ( function(task) {
        return function() {
            // execute the task, set status and return.
            // the task itself must return true if complete or false otherwise
            return (taskStatus = task() ? COMPLETE : inPROGRESS)
        }
    } )(tProc);

    function _id() { return id }
    function _name() { return tName }
    function _isComplete() { return (taskStatus == COMPLETE) }
    function _notStarted() { return (taskStatus == notSTARTED) }
    function _inProgress() { return (taskStatus == inPROGRESS) }
    function _isStopped() { return (taskStatus == STOPPED) }
    function _stop() { taskStatus = STOPPED; return this; }

    function _start() {
        if ( _isComplete() ) return this;    // job is complete
        taskStatus = inPROGRESS;

        // execute the task and set 'asynch' processing in place for the task
        wrappedTask();
        _inProgress() && fnNextTick(_start)
        return this;
    }

    return {
        id: _id,
        isComplete: _isComplete,
        notStarted: _notStarted,
        inProgress: _inProgress,
        start: _start,
        stop: _stop
    }
    
}

//*********************************
// this is our bg process.. note since we cant test true asynch, we simulate it
// the avail vble tells us when the bf process is done.  In reality the bg process itself
// should return true/false.  In our case since the we are using thw wait func, once  the
// process is entered, we hang till it completes. (rememebr we dont have true asynch!!)
var avail = false;
var bgProcess = function() {
    // the workload... note most internal tasks are sequential.
    // the only ones that are truly asynch are XHR types or
    // loading files related (eg via the script tag)

    console.log('bgProcess.. wait a mo. time = ', diff());
    wait(1000); // just wait
    console.log('yawn. time = ', diff());
    avail = true;
};
//*********************************


var baseTime = (new Date()).getTime();
var diff = function() { return ((new Date()).getTime() - baseTime); }

// a template function for the Task object
var egTask = Task('egTask', function() {
    if (avail) {
        // you can do some closing stuff here if you want!
        console.log('egTask closing stuff here...', diff());
    }
    return avail;
});

setTimeout(bgProcess, 500);  // simulate a bg task.  use timeout to simulate it being asynch
egTask.start();
//////////////////////////////////////////////////////////////////////////////////////


var notSTARTED = 0,
    inPROGRESS = 1,
    COMPLETE = 2,
    ABORTED = 3,
    ctr = 0;
    fnNextTick = function(f) {
        if (ctr++%100 == 0) console.log('Next Tick ...', ctr, diff());
        setTimeout(f, 1);
    },
    getuid = 1,
    wait = function(ms){
        ms += new Date().getTime();
        while (new Date() < ms){}
    };

var Task = function(tName, task, taskCompletion, taskAbort) {
    var id = getuid++,
        taskStatus = notSTARTED,
        tBase = (new Date()).getTime(), _timesUp,
        _wrappedTask, _wrappedChecker, _wrappedAborter;

    // Useful failsafe to stop neverending jobs!   expand this later ************
    _timesUp = function() { return ((new Date()).getTime() - tBase) > 5000 }

    _task = ( function(wTask) {
        return function() {
            wTask() ? _setComplete() : _setInProgress();
        }
    } )(task);

    _taskCompletion = ( function(wCheck) {
        return function() {
            _inProgress() && wCheck() && _setComplete();
            !(_isComplete() || _isAborted()) && _timesUp() && _setAbort();
            _inProgress() && fnNextTick(_taskCompletion);
        }
    } )(taskCompletion);

    _taskAbort = ( function(wAbort) {
        return function() {
            _inProgress() && wAbort();
            _setAbort();
        }
    } )(taskAbort);

    function _start() {
        // only kick-off the task if not started
        if ( !_notStarted() ) return this;
        //if ( _isComplete() || _inProgress() || _isAborted() ) return this;

        // simulate asynch by kicking off in a few mos.. this is purely for testing
        setTimeout(_task, 500);        // normally we just say _task();

        // kick off the check for completion process
        _taskCompletion();
        return this;
    }


    function _setAbort() { taskStatus = ABORTED; return this; }
    function _setComplete() { taskStatus = COMPLETE; return this;}
    function _setInProgress() { taskStatus = inPROGRESS; return this; }

    function _id() { return id }
    function _name() { return tName }    
    function _isComplete() { return (taskStatus == COMPLETE) }
    function _notStarted() { return (taskStatus == notSTARTED) }
    function _inProgress() { return (taskStatus == inPROGRESS) }
    function _isAborted() { return (taskStatus == ABORTED) }
    return {
        id: _id,
        isComplete: _isComplete,
        notStarted: _notStarted,
        inProgress: _inProgress,
        start: _start,
        abort: _setAbort
    }
    
}

var ctr = 0, fin = false;
var bgProcess = function() {
    // the workload... note most internal tasks are sequential.
    // the only ones that are truly asynch are XHR types or
    // loading files related (eg via the script tag)

    console.log('bgProcess.. subtask ', ctr, ' wait a mo...');
    wait(1000); // just wait
    console.log('yawn... hello..');
    return (fin = (ctr++ < 3))
};
var checker = function() {
}
var aborter = function() {
}
/////////////////////////////////////////////////////////////////////

var wait = function(ms){
        ms += new Date().getTime();
        while (new Date() < ms){}
    console.log('morning!!');
}
var ctr = 0;
var task = function() {
    console.log('waiting ', ctr);
    wait(1000);
    if (ctr++<4) {
        try{ setTimeout(task, 100); return false; }
        catch(e){ console.log(e.name, e.message); }
    }
    return true; // set the task flag??
}

var abort = function() {
    console.log('task deleted');
    delete task;
}
task();
abort(task);
typeof task;